#include "sys_gpio.h"
#include "sys_delay.h"

// Reference code form NXP:
// https://www.nxp.com/docs/en/application-note/AN12841.pdf

//------------------------------------------------------------------------------

// PE port
#ifdef EVB_IO
#define SCL_IO 11
#define SDA_IO 12
#else
#define SCL_IO 0
#define SDA_IO 1
#endif

#define i2c_SDA_Output() GPIO_Congif(GPIOE, SDA_IO, GPIO_Mode_OUT, GPIO_PuPd_UP)
#define i2c_SCL_Output() GPIO_Congif(GPIOE, SCL_IO, GPIO_Mode_OUT, GPIO_PuPd_UP)
#define i2c_SDA_Input()  GPIO_Congif(GPIOE, SDA_IO, GPIO_Mode_IN,  GPIO_PuPd_UP)
#define i2c_SCL_Input()  GPIO_Congif(GPIOE, SCL_IO, GPIO_Mode_IN,  GPIO_PuPd_UP)

#define i2c_SDA_Output_Off()
#define i2c_SDA_Input_Off()

#define i2c_SDA_High() GPIO_SET  (GPIOE, SDA_IO)
#define i2c_SDA_Low()  GPIO_RESET(GPIOE, SDA_IO)
#define i2c_SCL_High() GPIO_SET  (GPIOE, SCL_IO)
#define i2c_SCL_Low()  GPIO_RESET(GPIOE, SCL_IO)

#define i2c_SDA_PullUp()
#define i2c_SCL_PullUp()

#define i2c_SDA_Status() GPIO_READ(GPIOE, SDA_IO)

#define i2c_RepeatedStart() i2c_start()

#define i2c_DelayOne()  delay_us(8)
#define i2c_DelayHalf() delay_us(4)

//------------------------------------------------------------------------------

/* I/O initialization */
void i2c_init(void)
{
	i2c_SCL_High();
	i2c_SDA_High();
	i2c_SCL_Output();
	i2c_SDA_Output();
}

/* START condition */
void i2c_start(void)
{
	i2c_SCL_Low();
	i2c_SDA_High();
	i2c_SCL_High();

	i2c_DelayOne();

	/* SDA change from high to low when SCL is high, i2c start */
	i2c_SDA_Low();

	i2c_DelayHalf();
}

/* STOP condition */
void i2c_stop(void)
{
	i2c_SCL_Low();
	i2c_SDA_Low();

	i2c_DelayHalf();

	i2c_SCL_High();

	i2c_DelayOne();

	/* SDA change from low to high when SCL is high, i2c stop */
	i2c_SDA_High();
}

/* master reads ACK signal */
unsigned char i2c_rx_ack(void)
{
	unsigned char ack;

	/* the 9th clock start */
	i2c_SCL_Low();

	i2c_SDA_Output_Off();
	i2c_SDA_Input();

	i2c_DelayOne();
	i2c_SCL_High();

	/* read ACK signal when SCL is high */
	i2c_DelayHalf();
	ack = i2c_SDA_Status();
	i2c_DelayHalf();
	/* the 9th clock end */

	i2c_SDA_Input_Off();
	i2c_SDA_Output();

	/* SCL hold low to wait */
	i2c_SCL_Low();
	i2c_SDA_High();

	return ack;
}

/* master sends ACK to slave */
void i2c_tx_ack(void)
{
	/* the 9th clock start */
	i2c_SCL_Low();

	/*send ACK signal */
	i2c_DelayHalf();
	i2c_SDA_Low();
	i2c_DelayHalf();
	i2c_SCL_High();
	i2c_DelayOne();

	/* the 9th clock start */
	/* SCL hold low to wait */
	i2c_SCL_Low();
	i2c_SDA_High();
}

/* master sends NACK to slave */
void i2c_tx_nack(void)
{
	/* the 9th clock start*/
	i2c_SCL_Low();

	/*send NACK signal */
	i2c_DelayHalf();
	i2c_SDA_High();
	i2c_DelayHalf();
	i2c_SCL_High();
	i2c_DelayOne();

	/* the 9th clock start */
	/* SCL hold low to wait */
	i2c_SCL_Low();
	i2c_SDA_High();
}

/* master writes a byte to salve and check ACK signal */
int i2c_tx_byte(unsigned char data)
{
	unsigned char i;

	/* write 8 bits data */
	for (i = 0; i < 8; i++)
	{
		i2c_SCL_Low();

		i2c_DelayHalf();

		/* Data can be changed only while SCL is low */
		if (data & 0x80)
		{
			i2c_SDA_High();
		}
		else
		{
			i2c_SDA_Low();
		}

		i2c_DelayHalf();

		/* Data must be held stable while SCL is high */
		i2c_SCL_High();
		i2c_DelayOne();

		data = data << 1;
	}

	/* Must set SCL to low. If SCL is high, SDA change to High/Low will cause Stop/Start signal.*/
	i2c_SCL_Low();

	/* check ACK signal, ACK signal is 0, NACK signal is 1 */
	if (i2c_rx_ack() == 1)
	{
		/* receive NACK signal, stop data transfer */
		return -1;
	}

	return 0;
}

/* master reads a byte from slave */
unsigned char i2c_rx_byte(int ack)
{
	unsigned char i;
	unsigned char data = 0;

	i2c_SDA_Output_Off();
	i2c_SDA_Input();

	/* write 8 bits data */
	for (i = 0; i < 8; i++)
	{
		i2c_SCL_Low();
		i2c_DelayOne();

		/* Data must be held stable while SCL is high */
		i2c_SCL_High();
		data = data << 1;

		i2c_DelayHalf();

		/* read SDA data */
		if (i2c_SDA_Status())
		{
			data |= 0x01;
		}
		else
		{
			data &= ~0x01;
		}

		i2c_DelayHalf();
	}

	i2c_SDA_Input_Off();
	i2c_SDA_Output();

	i2c_SCL_Low(); // set SCL to low

	if (ack)
		i2c_tx_ack();
	else
		i2c_tx_nack();

	return data;
}
